use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::{env, fs, io};

use cargo_lock::{package::SourceKind, Lockfile};

fn get_vector_lock_path() -> PathBuf {
    let path = fs::canonicalize(env::var("CARGO_MANIFEST_DIR").unwrap()).unwrap();

    // Remove the "lib/vector-vrl/web-playground" suffix
    let parent_path = path
        .parent()
        .and_then(|p| p.parent())
        .and_then(|p| p.parent());
    parent_path
        .expect("Failed to find vector repo root")
        .join("Cargo.lock")
        .to_path_buf()
}

fn get_git_hash() -> String {
    let output = Command::new("git")
        .args(["rev-parse", "HEAD"])
        .output()
        .expect("Failed to get git HEAD sha");
    String::from_utf8(output.stdout).unwrap().trim().to_string()
}

fn create_const_statement(name: &str, value: &str) -> String {
    format!("pub const {name}: &str = \"{value}\";\n")
}

fn write_vector_constants(output_file: &mut File) {
    // TODO: For releases, we should use the manifest.package().version().
    // https://github.com/vectordotdev/vector/issues/18425
    let vector_git_sha = get_git_hash();
    let vector_version_statement = create_const_statement("VECTOR_VERSION", &vector_git_sha);
    output_file
        .write_all(vector_version_statement.as_bytes())
        .expect("Failed to write Vector version constant");

    let vector_link = format!("https://github.com/vectordotdev/vector/tree/{vector_git_sha}");
    let vector_link_statement = create_const_statement("VECTOR_LINK", &vector_link);
    output_file
        .write_all(vector_link_statement.as_bytes())
        .expect("Failed to write Vector version constant");
}

fn write_vrl_constants(lockfile: &Lockfile, output_file: &mut File) {
    let vrl_dep = lockfile
        .packages
        .iter()
        .find(|&package| package.name.as_str() == "vrl")
        .expect("missing VRL dependency");

    let vrl_source = vrl_dep.source.clone().expect("missing VRL source id");

    let (version, link) = match vrl_source.kind() {
        SourceKind::Git(_) => {
            let precise = vrl_source
                .precise()
                .expect("git reference should have precise")
                .to_string();
            (
                precise.clone(),
                format!("{}/tree/{precise}", vrl_source.url()),
            )
        }
        SourceKind::Registry if vrl_source.is_default_registry() => {
            let version = vrl_dep.version.to_string();
            (
                version.to_string(),
                format!("https://crates.io/crates/vrl/{version}"),
            )
        }
        SourceKind::Path
        | SourceKind::Registry
        | SourceKind::SparseRegistry
        | SourceKind::LocalRegistry
        | SourceKind::Directory => unimplemented!("unhandled source kind: {:?}", vrl_source.kind()),
        _ => unimplemented!("unknown source kind: {:?}", vrl_source.kind()),
    };

    output_file
        .write_all(create_const_statement("VRL_VERSION", &version).as_bytes())
        .expect("Failed to write VRL version constant");

    output_file
        .write_all(create_const_statement("VRL_LINK", &link).as_bytes())
        .expect("Failed to write VRL_LINK constant");
}

fn write_build_constants(lockfile: &Lockfile, dest_path: &Path) -> io::Result<()> {
    let mut output_file = File::create(dest_path)?;
    output_file.write_all(
        "// AUTOGENERATED CONSTANTS. SEE BUILD.RS AT REPOSITORY ROOT. DO NOT MODIFY.\n".as_ref(),
    )?;
    write_vector_constants(&mut output_file);
    write_vrl_constants(lockfile, &mut output_file);
    Ok(())
}

fn main() {
    let lockfile =
        Lockfile::load(get_vector_lock_path()).expect("Failed to load Vector Cargo.lock");
    let dst = Path::new(&env::var("OUT_DIR").unwrap()).join("built.rs");
    write_build_constants(&lockfile, &dst).expect("Failed to write constants");
}
